#pragma once

#include "exceptions.h"
#include "type.h"

#include <algorithm>
#include <iterator>
#include <ostream>



namespace PS
{

/**
 * A class representing a fixed size array of indices.
 *
 * It is used in tensorial objects like the TableBase and SymmetricTensor
 * classes to represent a nested choice of indices.
 *
 * @tparam N The number of indices stored in each object.
 *
 * @ingroup data
 */
template <int N>
class TableIndices
{
public:
  static_assert(N > 0,
                "TableIndices objects need to represent at least one index.");


  /**
   * Default constructor. This constructor sets all indices to zero.
   */
  constexpr TableIndices() = default;

  /**
   * Constructor. Initializes the indices stored by this object by the given
   * arguments @p indices
   *
   * This constructor will result in a compiler error if
   * the number of arguments given is different from the number of the
   * indices this class stores (i.e., the template argument `N` of
   * this class), or if any of the arguments is not of some integer type.
   */
  template <typename... T>
  constexpr TableIndices(const T... indices);

  /**
   * Read-only access the value of the <tt>i</tt>th index.
   */
  constexpr std::size_t
  operator[](const unsigned int i) const;

  /**
   * Write access the value of the <tt>i</tt>th index.
   */
  constexpr std::size_t &
  operator[](const unsigned int i);

  /**
   * Compare two index fields for equality.
   */
  constexpr bool
  operator==(const TableIndices<N> &other) const;

  /**
   * Compare two index fields for inequality.
   */
  constexpr bool
  operator!=(const TableIndices<N> &other) const;

  /**
   * Sort the indices in ascending order. While this operation is not very
   * useful for Table objects, it is used for the SymmetricTensor class.
   */
   void
  sort();

protected:
  /**
   * Store the indices in an array.
   */
  std::size_t indices[N]{};
};



/* --------------------- Template and inline functions ---------------- */


namespace internal
{
  namespace TemplateConstraints
  {
    // TODO: Once we are able to use DEAL_II_HAVE_CXX17, the following classes
    // can be made much simpler with the help of fold expressions, see
    // https://en.cppreference.com/w/cpp/language/fold

    // helper struct for is_base_of_all and all_same_as
    template <bool... Values>
    struct BoolStorage;


    /**
     * A helper class whose `value` member is true or false depending on
     * whether all of the given boolean template arguments are `true`.
     * The class works by comparing the list of boolean values
     * `true, Values...` with the list `Values..., true` (i.e., with
     * its rotated self). The two are only the same if `Values...` is
     * a list of only `true` values.
     */
    template <bool... Values>
    struct all_true
    {
      static constexpr bool value =
        std::is_same<BoolStorage<Values..., true>,
                     BoolStorage<true, Values...>>::value;
    };


    /**
     * A class whose `value` member is set to `true` if any of the
     * boolean template arguments are true.
     */
    template <bool... Values>
    struct any_true;


    template <bool V1, bool... Values>
    struct any_true<V1, Values...>
    {
      static constexpr bool value = V1 || any_true<Values...>::value;
    };


    template <>
    struct any_true<>
    {
      static constexpr bool value = false;
    };
  } // namespace TemplateConstraints

} // namespace internal



template <int N>
template <typename... T>
constexpr TableIndices<N>::TableIndices(const T... args)
  : indices{static_cast<std::size_t>(args)...}
{
  static_assert(internal::TemplateConstraints::all_true<
                  std::is_integral<T>::value...>::value,
                "Not all of the parameters have integral type!");
  static_assert(sizeof...(T) == N, "Wrong number of constructor arguments!");
}


template <int N>
constexpr inline std::size_t
TableIndices<N>::operator[](const unsigned int i) const
{
  AssertIndexRange(i, N);
  return indices[i];
}


template <int N>
constexpr inline std::size_t &
TableIndices<N>::operator[](const unsigned int i)
{
  AssertIndexRange(i, N);
  return indices[i];
}


template <int N>
constexpr bool
TableIndices<N>::operator==(const TableIndices<N> &other) const
{
  return std::equal(std::begin(indices),
                    std::end(indices),
                    std::begin(other.indices));
}


template <int N>
constexpr bool
TableIndices<N>::operator!=(const TableIndices<N> &other) const
{
  return !(*this == other);
}


template <int N>
 inline void
TableIndices<N>::sort()
{
  std::sort(std::begin(indices), std::end(indices));
}



/**
 * Output operator for TableIndices objects; reports them in a list like this:
 * <code>[i1,i2,...]</code>.
 *
 * @relatesalso TableIndices
 */
template <int N>
std::ostream &
operator<<(std::ostream &out, const TableIndices<N> &indices)
{
  out << '[';
  for (unsigned int i = 0; i < N; ++i)
    {
      out << indices[i];
      if (i + 1 != N)
        out << ',';
    }
  out << ']';

  return out;
}

} // namespace PS